﻿/*********************************************************************************
**                                                                              **
**  Copyright (C) 2022-2022 LiLong                                              **
**  This file is part of BluetoothCpp.                                          **
**                                                                              **
**  BluetoothCpp is free software: you can redistribute it and/or modify        **
**  it under the terms of the GNU Lesser General Public License as published by **
**  the Free Software Foundation, either version 3 of the License, or           **
**  (at your option) any later version.                                         **
**                                                                              **
**  BluetoothCpp is distributed in the hope that it will be useful,             **
**  but WITHOUT ANY WARRANTY; without even the implied warranty of              **
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               **
**  GNU Lesser General Public License for more details.                         **
**                                                                              **
**  You should have received a copy of the GNU Lesser General Public License    **
**  along with BluetoothCpp.  If not, see <https://www.gnu.org/licenses/>.      **
**********************************************************************************/
#include "AuthenticationWin.h"
#include "../Address.h"
#include "../Device.h"

#ifdef WIN32
#include <WinSock2.h>
#include <bluetoothapis.h>
#include <ws2bth.h>

#include "ErrorHandleWin.h"
#include "Utils.h"

#include <shared_mutex>

namespace BTH
{

struct AuthenticationWin::Impl
{
    HBLUETOOTH_AUTHENTICATION_REGISTRATION hb;
    std::function<bool(const Device&, unsigned long)> cb;
    std::shared_mutex mutex;
    BLUETOOTH_AUTHENTICATE_RESPONSE resp;

    static BOOL CALLBACK callback(LPVOID pvParam, PBLUETOOTH_AUTHENTICATION_CALLBACK_PARAMS pAuthCallbackParams)
    {
        AuthenticationWin::Impl* impl = reinterpret_cast<AuthenticationWin::Impl*>(pvParam);
        std::shared_lock lock(impl->mutex);
        const auto& info = pAuthCallbackParams->deviceInfo;
        Device dev(info);
        bool au = false;
        if (impl->cb)
        {
            au = std::invoke(impl->cb, dev, pAuthCallbackParams->Passkey);
        }
        ZeroMemory(&impl->resp, sizeof(m_impl->resp));
        impl->resp.bthAddressRemote = pAuthCallbackParams->deviceInfo.Address;
        impl->resp.authMethod       = pAuthCallbackParams->authenticationMethod;
        impl->resp.negativeResponse = !au;
        auto ret                    = BluetoothSendAuthenticationResponseEx(nullptr, &impl->resp);
        return TRUE;
    }
};

AuthenticationWin::AuthenticationWin() : m_impl(std::make_unique<Impl>()) {}

AuthenticationWin::~AuthenticationWin()
{
    if (m_listening)
        stopListen();
}

void AuthenticationWin::listen(const std::function<bool(const Device&, unsigned long)>& cb)
{
    if (auto ret = BluetoothRegisterForAuthenticationEx(nullptr, &m_impl->hb, &Impl::callback, m_impl.get()); ret != ERROR_SUCCESS)
        throwError(ret);
    m_listening = true;
    m_impl->cb  = cb;
}

void AuthenticationWin::stopListen()
{
    BluetoothUnregisterAuthentication(m_impl->hb);
    m_listening = false;
}

bool AuthenticationWin::isConnectable() const { return BluetoothIsConnectable(nullptr); }

bool AuthenticationWin::isDiscoverable() const { return BluetoothIsDiscoverable(nullptr); }

void AuthenticationWin::requestPair(const Device& dev, unsigned short pin)
{
    if (dev.isAuthenticated())
        return;
    BLUETOOTH_DEVICE_INFO info;
    ZeroMemory(&info, sizeof(info));
    info.Address.ullLong = dev.address().toUInt64();
    info.fAuthenticated  = dev.isAuthenticated();
    info.fConnected      = dev.isConnected();
    info.fRemembered     = dev.isRemembered();
    info.dwSize          = sizeof(info);
    if (DWORD ret =
            BluetoothAuthenticateDeviceEx(nullptr, nullptr, &info, nullptr, AUTHENTICATION_REQUIREMENTS::MITMProtectionRequiredBonding);
        ret != ERROR_SUCCESS)
        throwError(ret);
    std::unique_lock lock(m_impl->mutex);
}

} // namespace BTH
#endif
